package org.onionframework.persistence.dao.dynamic;

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.PostConstruct;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang3.Validate;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.onionframework.core.config.SystemConfig;
import org.onionframework.core.util.Exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.xml.sax.InputSource;

import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;

/**
 * 这里加载全部的动态查询语句，建立Freemarker的Template对象，包装秤StatementTemplate，提供给外部使用
 * 
 * @author WangXuzheng
 * @author allanz
 *
 */
@Component
public class StatementTemplateSupport implements ResourceLoaderAware {
	
	private static final Logger logger = LoggerFactory.getLogger(StatementTemplateSupport.class);
	/** 存放了hql语句集合 */
	private Map<String, String> namedHQLQueries;
	/** 存放了sql语句集合 */
	private Map<String, String> namedSQLQueries;
	/** 存放了查询片段，主要用于初始化 namedHQLQueries和namedSQLQueries */
	private Map<String, String> namedFragmentQueries;
	
	private Map<String,String> resultTypes;
	private String[] fileNames = new String[0];
	private ResourceLoader resourceLoader;
	//private EntityResolver entityResolver = null;//new DynamicStatementDTDEntityResolver();
	
	private Map<String,StatementTemplate> templates = new HashMap<String,StatementTemplate>();
	
	/**
	 * 查询语句名称缓存，不允许重复
	 */
	private Set<String> nameCache = new HashSet<String>();

	@PostConstruct
	public void init() throws IOException {
		namedHQLQueries = new HashMap<String, String>();
		namedSQLQueries = new HashMap<String, String>();
		namedFragmentQueries = new HashMap<String, String>();
		resultTypes = new HashMap<String, String>();
		
		boolean flag = this.resourceLoader instanceof ResourcePatternResolver;
		this.fileNames = SystemConfig.Config.getStringArray("onion.persistence.dynamic_namedquery_files");
		if(ArrayUtils.isEmpty(this.fileNames))
			fileNames = new String[]{"classpath*:/**/*-dynamic.xml"};
		
		for (String file : fileNames) {
			if (flag) {
				Resource[] resources = ((ResourcePatternResolver) this.resourceLoader).getResources(file);
				buildMap(resources);
			} else {
				Resource resource = resourceLoader.getResource(file);
				buildMap(resource);
			}
		}
		logger.info("执行fragment语句替换");
		//在这里要把include的语句加入到已经解析出来的sql和hql中去。
		//		$$$include name=""$$$
		//正则表达式Pattern pattern = Pattern.compile("\\$\\$\\$include\\s+name\\s*=\\s*\"(.[^\\$]+)\"\\s*\\$\\$\\$");
		replaceFragment(namedHQLQueries);
		replaceFragment(namedSQLQueries);
		
		
		//到这里namedFragmentQueries已经没有用处了，是否销毁？
		//如果允许运行时修改语句，则不用销毁，但是运行是修改语句的话，片段是否也会被修改？
		//先销毁吧
		namedFragmentQueries.clear();
		namedFragmentQueries = null;
		
		//clear name cache
		nameCache.clear();
		
		this.initTemplates();
	}

	private void initTemplates() throws IOException {
		 Configuration configuration = new Configuration();  
	        configuration.setNumberFormat("#");
	        StringTemplateLoader stringLoader = new StringTemplateLoader();  
	        for(Entry<String, String> entry : this.namedHQLQueries.entrySet()){  
	            stringLoader.putTemplate(entry.getKey(), entry.getValue());  
	            templates.put(entry.getKey(), new StatementTemplate(StatementType.HQL,
	            		new Template(entry.getKey(),new StringReader(entry.getValue()),configuration)));  
	        }  
	        for(Entry<String, String> entry : this.namedSQLQueries.entrySet()){  
	            stringLoader.putTemplate(entry.getKey(), entry.getValue());  
	            templates.put(entry.getKey(), new SqlStatementTemplate(StatementType.SQL,
	            		new Template(entry.getKey(),new StringReader(entry.getValue()),configuration),this.resultTypes.get(entry.getKey())));  
	        }  
	        configuration.setTemplateLoader(stringLoader);  		
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	private void buildMap(Resource[] resources) throws IOException {
		if (resources == null) {
			return;
		}
		for (Resource resource : resources) {
			buildMap(resource);
		}
	}

	@SuppressWarnings({ "rawtypes" })
	private void buildMap(Resource resource) {
		InputSource inputSource = null;
		try {
			inputSource = new InputSource(resource.getInputStream());
			SAXReader saxReader = new SAXReader();
			//saxReader.setEntityResolver( entityResolver );
			//saxReader.setErrorHandler( errorHandler );
			saxReader.setMergeAdjacentText( true );
			Document document = null;
			document = saxReader.read(inputSource);
				
			if (isDynamicStatementXml(document)) {
				final Element dynamicHibernateStatement = document.getRootElement();
				Iterator rootChildren = dynamicHibernateStatement.elementIterator();
				while (rootChildren.hasNext()) {
					final Element element = (Element) rootChildren.next();
					final String elementName = element.getName();
					if ("sql-query".equals(elementName)) {
						putStatementToCacheMap(resource, element, namedSQLQueries);
					} else if ("hql-query".equals(elementName)) {
						putStatementToCacheMap(resource, element, namedHQLQueries);
					} else if("fragment".equals(elementName)){
						//如果是片段，怎么怎么怎么
						putFragmentToFragmentMap(resource, element);
					}
				}
				
				logger.info("加载了dynamic-hibernate-statement文件：{}",resource.getURI().toString());
			}
		} catch (Exception e) {
			throw Exceptions.unchecked(e);
		} finally {
			if (inputSource != null && inputSource.getByteStream() != null) {
				try {
					inputSource.getByteStream().close();
				} catch (IOException e) {
					throw Exceptions.unchecked(e);
				}
			}
		}

	}

	private void putStatementToCacheMap(Resource resource, final Element element, Map<String, String> statementMap)
			throws IOException {
		String sqlQueryName = element.attribute("name").getText();
		Validate.notEmpty(sqlQueryName);
		if (nameCache.contains(sqlQueryName)) {
			throw new RuntimeException("重复的sql-query/hql-query语句定义" + sqlQueryName + "在文件:" + resource.getURI() + "中，必须保证name的唯一.");
		}
		nameCache.add(sqlQueryName);
		String queryText = element.getText();
		statementMap.put(sqlQueryName, queryText);
		if(element.attribute("resultType") != null){
			this.resultTypes.put(sqlQueryName, element.attributeValue("resultType"));
		}
	}
	/**
	 * 将语句片段放到片段集合中
	 * @param resource
	 * @param element
	 * @throws IOException
	 */
	private void putFragmentToFragmentMap(Resource resource, final Element element)
			throws IOException {
		String sqlQueryName = element.attribute("name").getText();
		Validate.notEmpty(sqlQueryName);
		if (namedFragmentQueries.containsKey(sqlQueryName)) {
			throw new RuntimeException("重复的fragment语句定义" + sqlQueryName + "在文件:" + resource.getURI() + "中，必须保证name的唯一.");
		}
		
		String queryText = element.getText();
		namedFragmentQueries.put(sqlQueryName, queryText);
	}
	/**
	 * 替换hql或sql中的语句片段
	 * @param namedQueries
	 */
	private void replaceFragment(Map<String, String> namedQueries){
		Pattern pattern = Pattern.compile("\\$\\$\\$include\\s+name\\s*=\\s*\"(.[^\\$]+)\"\\s*\\$\\$\\$");
		for(Map.Entry<String,String> entry : namedQueries.entrySet()){
			String text = entry.getValue();
			logger.debug("替换语句:"+text);
			Matcher matcher = pattern.matcher(text);
			while(matcher.find()){
				String fragmentName = matcher.group(1);
				String fragmentQuery = namedFragmentQueries.get(fragmentName);
				//校验
				Validate.notBlank(fragmentQuery);
				//要被替换的字符串
				//String includeQuery = matcher.group(0);
				logger.debug("替换名称为"+fragmentName+"的fragment语句:"+fragmentQuery);
				text = matcher.replaceFirst(fragmentQuery);
				
				//text = text.replaceFirst(includeQuery, fragmentQuery);
			}
			namedQueries.put(entry.getKey(), text);
		}
	}
	/**
	 * 通过根节点名称判断文档是否是动态语句的文本
	 * @param xmlDocument
	 * @return
	 */
	private static boolean isDynamicStatementXml(Document xmlDocument) {
		return "dynamic-hibernate-statement".equals(xmlDocument.getRootElement().getName());
	}

	/**
	 * 获取一个语句模板
	 * @param name
	 * @return
	 */
	public StatementTemplate getStatementTemplate(String name) {
		return this.templates.get(name);
	}
	
}